using System;
using System.Collections;
using System.Reflection;

namespace UnityEngine
{

internal class SendMouseEvents
{
	struct HitInfo
	{
		public GameObject target;
		public Camera camera;
		
		public void SendMessage (string name)
		{
			target.SendMessage (name, null, SendMessageOptions.DontRequireReceiver);	
		}
		
		public static implicit operator bool (HitInfo exists)
		{
			return exists.target != null && exists.camera != null;
		}
	
		public static bool Compare (HitInfo lhs, HitInfo rhs)
		{
			return lhs.target == rhs.target && lhs.camera == rhs.camera;
		}
	}

	static HitInfo[] m_LastHit = { new HitInfo (), new HitInfo () };
	static HitInfo[] m_MouseDownHit = { new HitInfo (), new HitInfo () };
	static RaycastHit2D[] m_MouseRayHits2D = {new RaycastHit2D()};

	[NotRenamed]
	static void DoSendMouseEvents (int mouseUsed, int skipRTCameras)
	{
		HitInfo[] currentHit = { new HitInfo (), new HitInfo () };
		Vector3 mousePosition = Input.mousePosition;
		Camera[] cameras = Camera.allCameras;

		// If UnityGUI has the mouse over, we simply don't do any mouse hit detection.
		// That way, it will appear as if the mouse has missed everything.
		if (mouseUsed == 0)
		{
			foreach (Camera camera in cameras)
			{
				// we do not want to check cameras that are rendering to textures, starting with 4.0
				if (skipRTCameras != 0 && camera.targetTexture != null)
					continue;

				// Is the mouse inside the cameras viewport?
				Rect rect = camera.pixelRect;
				if (!rect.Contains (mousePosition))
					continue;

				// Did we hit any gui elements?
				GUILayer layer = (GUILayer)camera.GetComponent (typeof (GUILayer));
				if (layer)
				{
					GUIElement element = layer.HitTest (mousePosition);
					if (element)
					{
						currentHit[0].target = element.gameObject;
						currentHit[0].camera = camera;
					}
				}
#if ENABLE_PHYSICS || ENABLE_2D_PHYSICS
				// There is no need to continue if the camera shouldn't be sending out events
				if ((int)camera.eventMask == 0)
					continue;

#if ENABLE_PHYSICS				
				// Did we hit any colliders?
				RaycastHit hit;
				if (camera.farClipPlane > 0.0f && Physics.Raycast (camera.ScreenPointToRay (mousePosition),
					out hit, camera.farClipPlane, camera.cullingMask & camera.eventMask & Physics.DefaultRaycastLayers
				))
				{
					if (hit.rigidbody)
					{
						currentHit[1].target = hit.rigidbody.gameObject;
						currentHit[1].camera = camera;
					}
					else
					{
						currentHit[1].target = hit.collider.gameObject;
						currentHit[1].camera = camera;
					}
				}
#endif
#if ENABLE_2D_PHYSICS
				// Did we hit any 2D colliders?
				else if (camera.farClipPlane > 0.0f)
				{
					// Did we hit any 2D colliders?
					if (Physics2D.GetRayIntersectionNonAlloc (camera.ScreenPointToRay (mousePosition),
						m_MouseRayHits2D, camera.farClipPlane, camera.cullingMask & camera.eventMask & Physics2D.DefaultRaycastLayers) == 1)
					{
						currentHit[1].camera = camera;
						if (m_MouseRayHits2D[0].rigidbody)
							currentHit[1].target = m_MouseRayHits2D[0].rigidbody.gameObject;
						else
							currentHit[1].target = m_MouseRayHits2D[0].collider.gameObject;
					}
				}
#endif
				else
				{
					// We did not hit anything with a raycast from this camera. But our camera
					// clears the screen and renders on top of whatever was below, thus making things
					// rendered before invisible. So clear any previous hit we have found.
					if (camera.clearFlags == CameraClearFlags.Skybox || camera.clearFlags == CameraClearFlags.Color)
					{
						currentHit[1].target = null;
						currentHit[1].camera = null;
					}
				}
#endif
			}
		}
	
		// i=0: CameraGUILayer, i=1: 3D Objects
		for (int i=0;i<2;i++)
			SendEvents(i, currentHit[i]);
	}

	/// <summary>
	/// Old-style mouse events used prior to the new event system of 4.2.
	/// </summary>

	static void SendEvents (int i, HitInfo hit)
	{
		// Handle MouseDown, MouseDrag, MouseUp
		bool mouseDownThisFrame = Input.GetMouseButtonDown(0);
		bool mousePressed = Input.GetMouseButton(0);

		if (mouseDownThisFrame)
		{
			if (hit)
			{
				m_MouseDownHit[i] = hit;
				m_MouseDownHit[i].SendMessage("OnMouseDown");
			}
		}
		else if (!mousePressed)
		{
			if (m_MouseDownHit[i])
			{
				// For button like behavior only fire this event if same as on MouseDown
				if (HitInfo.Compare(hit, m_MouseDownHit[i]))
					m_MouseDownHit[i].SendMessage("OnMouseUpAsButton");

				// For backwards compatibility we keep the event name OnMouseUp
				m_MouseDownHit[i].SendMessage("OnMouseUp");
				m_MouseDownHit[i] = new HitInfo();
			}
		}
		else if (m_MouseDownHit[i])
		{
			m_MouseDownHit[i].SendMessage("OnMouseDrag");
		}


		// Handle MouseOver, MouseEnter, MouseExit
		if (HitInfo.Compare(hit, m_LastHit[i]))
		{
			if (hit)
				hit.SendMessage("OnMouseOver");
		}
		else
		{
			if (m_LastHit[i])
			{
				m_LastHit[i].SendMessage("OnMouseExit");
			}

			if (hit)
			{
				hit.SendMessage("OnMouseEnter");
				hit.SendMessage("OnMouseOver");
			}
		}
		m_LastHit[i] = hit;
	}
}
}
